Paper-Detecting Curve Text in the Wild-New Dataset and New Solution

论文阅读。

资源

正文

Abstract

提出了:

  • 一个数据集 CTW 1500(1000 张用于训练,500 张用于测试)

  • 基于该数据集提出了一种基于多边形的曲线文本检测器(CTD)

1 Introduction

png

目前的数据集很少有曲线文本,用四边形标记这种文本是有缺陷的,更不用说矩形了。例如,如图 1 所示,使用曲线边界框有三个显著的优点:

  • 避免重叠
  • 减小背景噪声
  • 避免使用多个文本行

对于所有类型的曲线文本区域,一个 14 点的多边形就足以定位它们。

在所提出的数据集上,结果表明,具有轻度减少的 resnet-50 的 CTD 可以有效地检测曲线文本。

目前的技术检测曲线文本都不好使。

3. CTW1500 Dataset and Annotation

Data decription

数据描述。CTW1500 数据集包含

  • 1500 幅图像
  • 10751 个边界框(3530 个是曲线边界框)
  • 每幅图像至少一个曲线文本。
  • 多语言,主要包含中文和英文

Annotation

png

为了包围曲线文本,我们创建了十条等距的参考线来帮助标记额外的 10 个点(我们实际上发现额外的 10 点足以标记所有类型的曲线文本)。我们使用等距线的原因是为了简化标注工作,并减少主观干扰。

一个曲线文本比标记四边形文本消耗大约三倍的时间。

4.1. Network Architecture

png

我们 CTD 的总体架构如图 4 所示,可分为三个部分:backbone、RPN 和 regression module。

  • backbone 通常采用 ImageNet 预先训练的流行模型,然后使用相应的模型进行微调,如 VGG-16、ResNet 等。区域建议网络(RPN)和回归模块分别连接到主干;
  • RPN 生成粗略回忆文本的建议;
  • regression module 对建议进行微调,使其更加紧凑。

4.2. Recurrent Transverse and Longitudinal Offset Connection (TLOC)

4.3. Long Side Interpolation

4.4. Polygonal Post Processing

非多边形抑制(NPS)。误报检测结果是制约文本检测性能的重要原因之一。

多边形非最大值抑制(PNMS)。非极大值抑制被证明对目标检测任务非常有效。

5. Experiments

提出的模型有效。

6. Conclusions and Future Work

数据集

从 Yuliang-Liu/Curve-Text-Detector: This repository provides train&test code, dataset, det.&rec. annotation, evaluation script, annotation tool, and ranking. (github.com) 下载数据集:

  • train_images.zip 训练集图像(1000 张)
  • ctw1500_train_labels.zip(训练集图像的标注文件,xml 格式)
  • test_images.zip 测试集图像(500 张)
  • gt_ctw1500.zip(测试集图像的标注文件,txt 格式)

可视化训练集图像的代码:

python
import xml.etree.ElementTree as ET
 
index = 997
 
# 定义路径
image_dir = r'E:\dataset\CTW1500\ctw1500\train_images\\'
label_dir = r"E:\dataset\CTW1500\ctw1500\ctw1500_train_labels\\"
 
image_path = os.path.join(image_dir, "{:04d}".format(index) + '.jpg')
label_path = os.path.join(label_dir, "{:04d}".format(index) + '.xml')
 
image_origin = cv2.imread(image_path)
image = image_origin.copy()
height, width, _ = image.shape
 
# 解析 XML 文件
tree = ET.parse(label_path)
root = tree.getroot()
 
# 遍历每个图像
for image_elem in root.findall("image"):
    file_name = image_elem.get("file")
    
    # 遍历每个标注框
    for box_elem in image_elem.findall("box"):
        box_height = int(box_elem.get('height'))
        box_width = int(box_elem.get('width'))
        box_left = int(box_elem.get('left'))
        box_top = int(box_elem.get('top'))
        
        print(f"Box height: {box_height}")
        print(f"Box width: {box_width}")
        print(f"Box left: {box_left}")
        print(f"Box top: {box_top}")  
 
        # 读取 <segs> 的值并以逗号分割成列表
        segs = box_elem.find('./segs')
        segs_values = segs.text.split(',')
 
        segs_x = []
        segs_y = []
        print("Segments:")
        for i in range(0, len(segs_values), 2):
            segs_x.append(int(segs_values[i]))
            segs_y.append(int(segs_values[i+1]))
        segs = np.array([segs_x, segs_y], np.int32).T
        print(f"segs_x: {segs_x}, y: {segs_y}")
        
        transcriptions = box_elem.find("label").text
        
        # 获取所有 pts 标签
        pts_elems = box_elem.findall("pts")
        
        # 提取坐标信息
        pts_x = []
        pts_y = []
        for pts_elem in pts_elems:
            pts_x.append(int(pts_elem.get("x")))
            pts_y.append(int(pts_elem.get("y")))
        pts = np.array([pts_x, pts_y], np.int32).T
        # 打印标注信息
        print("File: ", file_name)
        print("Transcriptions: ", transcriptions)
        print("Points: ", pts)
        
        # 画图
        cv2.rectangle(image, (box_left, box_top), (box_left + box_width, box_top + box_height), (255, 255, 0), thickness=2)
        cv2.polylines(image, [segs], isClosed=False, color=(255, 255, 255), thickness=2)
        cv2.polylines(image, [pts], isClosed=False, color=(255, 0, 0), thickness=2)
        
        for p in pts:
            cv2.circle(image, (p[0], p[1]), int(min(height, width) / 150), (0, 255, 255), -1)
            
        cv2.putText(image, transcriptions, (pts_x[0], pts_y[0] - int(min(height, width) / 150)), cv2.FONT_HERSHEY_SIMPLEX,
                    min(height, width) / 1000, (0, 255, 0), int(min(height, width) / 500))
        
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 9))
axes = axes.flatten()
 
axes[0].imshow(cv2.cvtColor(image_origin, cv2.COLOR_BGR2RGB))
axes[0].axis('off')
axes[0].set_title('Origin')
 
axes[1].imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
axes[1].axis('off')
axes[1].set_title('Annotation')
 
plt.tight_layout()
plt.show()
Box height: 108
Box width: 565
Box left: 201
Box top: 351
Segments:
segs_x: [216, 302, 389, 476, 563, 651, 739, 766, 671, 576, 482, 388, 294, 201], y: [351, 383, 398, 407, 409, 391, 363, 406, 440, 458, 459, 451, 432, 398]
File:  0997.jpg
Transcriptions:  VIOLENCE NEVER BRINGS
Points:  [[228 381]
 [245 390]
 [268 395]
 [289 406]
 [323 415]
 [352 422]
 [388 426]
 [417 431]
 [439 432]
 [473 436]
 [508 434]
 [540 433]
 [566 432]
 [601 429]
 [616 425]
 [634 421]
 [666 414]
 [683 406]
 [697 402]
 [722 392]
 [741 387]]
Box height: 111
Box width: 529
Box left: 228
Box top: 434
Segments:
segs_x: [251, 331, 411, 491, 571, 651, 731, 757, 669, 581, 493, 405, 316, 228], y: [437, 460, 474, 484, 479, 459, 434, 489, 528, 540, 545, 533, 522, 499]
File:  0997.jpg
Transcriptions:  PERMANENT PEACE
Points:  [[259 475]
 [290 484]
 [319 487]
 [358 495]
 [396 505]
 [435 507]
 [472 511]
 [505 512]
 [543 513]
 [578 506]
 [613 499]
 [645 493]
 [682 481]
 [709 471]
 [731 464]]
Box height: 96
Box width: 37
Box left: 466
Box top: 127
Segments:
segs_x: [501, 501, 501, 502, 502, 502, 503, 466, 466, 466, 467, 467, 467, 468], y: [127, 143, 159, 175, 191, 207, 223, 220, 204, 189, 173, 158, 142, 127]
File:  0997.jpg
Transcriptions:  LOC
Points:  [[486 141]
 [484 168]
 [485 205]]
png

可视化测试集图像的代码:

python
import cv2
import os
import matplotlib.pyplot as plt
 
index = 1200
 
image_dir = r'E:\dataset\CTW1500\ctw1500\test_images\\'
label_dir = r'E:\dataset\CTW1500\ctw1500\gt_ctw1500\\'
 
image_path = os.path.join(image_dir, "{:04d}".format(index) + '.jpg')
label_path = os.path.join(label_dir, "{:07d}".format(index) + '.txt')
 
image_origin = cv2.imread(image_path)
image = image_origin.copy()
height, width, _ = image.shape
label_file = open(label_path, 'r')
annotations = label_file.readlines()
label_file.close()
 
for annotation in annotations:
    coords_text = annotation.strip().split(',####')
    coords = list(map(int, coords_text[0].split(',')))
    points = np.array([(coords[i], coords[i+1]) for i in range(0, len(coords), 2)])
    transcriptions = coords_text[1]
        
    cv2.polylines(image, [points], isClosed=True, color=(255, 0, 0), thickness=2)
    
    for p in points:
        cv2.circle(image, (p[0], p[1]), int(min(height, width) / 150), (0, 255, 255), -1)
 
    cv2.putText(image, transcriptions, (points[0][0], points[0][1] - int(min(height, width) / 150)), cv2.FONT_HERSHEY_SIMPLEX,
                min(height, width) / 1000, (0, 255, 0), int(min(height, width) / 500))
    
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 9))
axes = axes.flatten()
 
axes[0].imshow(cv2.cvtColor(image_origin, cv2.COLOR_BGR2RGB))
axes[0].axis('off')
axes[0].set_title('Origin')
 
axes[1].imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
axes[1].axis('off')
axes[1].set_title('Annotation')
 
plt.tight_layout()
plt.show()
png